Multilingual Manager¶
Every model registered for translation is patched so that all its managers become subclasses of MultilingualManager (of course, if a custom manager is defined on the model, its functions is retained). MultilingualManager simplifies language-aware queries, especially on third-party apps, by rewriting query field names.
Every model manager is patched, not only objects but also managers inherited from abstract base classes.
For example:
# Assuming the current language is "pt",
# these queries returns the same objects
Foo1 = Foo.objects.filter(introduction__contains='Welcome')
Foo2 = Foo.objects.filter(introduction_pt__contains='Welcome')
assert Foo1 == Foo2
It works this way: the translation field name is used, it is changed into the current language field name, based on the current language. Any language-suffixed fields are left untouched, so title_es wouldn’t change, no matter what the current language is.
Rewriting of field names works with operators (like __in, __ge) as well as with
relationship spanning. Moreover, it is also handled on Q and F expressions.
These manager methods perform rewriting:
filter(),exclude(),get()order_by()update()only(),defer()values(),values_list(), with fallback mechanismdates()select_related()create(), with optional auto-population feature
In order not to introduce differences between X.objects.create(...) and X(...), model
constructor is also patched and performs rewriting of field names prior to regular initialization.
If one wants to turn rewriting of field names off, this can be easily achieved with
rewrite(mode) method. mode is a boolean specifying whether rewriting should be applied.
It can be changed several times inside a query. So X.objects.rewrite(False) turns rewriting off.
MultilingualManager offers one additional method: raw_values. It returns actual values from
the database, without field names rewriting. Useful for checking translated field database value.
Auto-population¶
There is special manager method populate mode `` which can trigger ``create() or
get_or_create() to populate all translation language fields with values from translated original ones. It can be very convenient when working with many languages. So:
x = Foo.objects.populate(True).create(title='bar')
is equivalent of:
x = Foo.objects.create(title_pt='bar', title_es='bar', title_fr='bar') ## title_?? for every language
Moreover, some fields can be explicitly assigned different values:
x = Foo.objects.populate(True).create(title='-- no translation yet --', title_es='hay traducción todavía')
It will result in title_es == 'hay traducción todavía' and other title_?? == '-- no translation yet --'.
There is another way of altering the current population status, an auto_populate context
manager:
from modeltranslation.utils import auto_populate
with auto_populate(True):
x = Foo.objects.create(title='bar')
Auto-population takes place also in model constructor, what is extremely useful when loading non-translated fixtures. Just remember to use the context manager:
with auto_populate(): # True can be ommited
call_command('loaddata', 'fixture.json') # Some fixture loading
z = Foo(title='bar')
print z.title_pt, z.title_es, z.title_fr # prints 'bar bar bar'
There is a more convenient way than calling populate manager method or entering
auto_populate manager context all the time: Auto populate
setting. It controls the default population behaviour.
Auto-population modes¶
There are four different population modes:
False[set by default]
Auto-population turned off
Trueor'all'[default argument to population altering methods]
Auto-population turned on, copying translated field value to all other languages (unless a translation field value is provided)
'default'- Auto-population turned on, copying translated field value to default language field (unless its value is provided)
'required'- Acts like
'default', but copy value only if the original field is non-nullable
Falling back¶
Modeltranslation provides a mechanism to control behaviour of data access in case of empty
translation values. This mechanism affects field access, as well as values() and values_list() manager methods.
Here is an example: a writer of some news hasn’t specified a French title and content, but only the Spanish and Portuguese ones. Then if a French visitor is viewing the site, we would rather show him English title/content of the news than having empty strings displayed. This is called fallback.
news.title_en = 'English title'
news.title_fr = ''
print news.title
# If current active language is French, it should display the title_de field value ('').
# But if fallback is enabled, it would display 'English title' instead.
# Similarly for manager
news.save()
print News.objects.filter(pk=news.pk).values_list('title', flat=True)[0]
# As above: if current active language is French and fallback to English is enabled,
# it would display 'English title'.
There are several ways of controlling fallback, described below.
Fallback languages¶
Fallback languages setting allows to set the order of fallback
languages. By default that’s the DEFAULT_LANGUAGE.
For example, setting
MODELTRANSLATION_FALLBACK_LANGUAGES = ('en', 'es')
means: if current active language field value is unset, try English value. If it is also unset, try Portuguese, and so on - until some language yields a non-empty value of the field.
There is also an option to define a fallback by language, using dict syntax:
MODELTRANSLATION_FALLBACK_LANGUAGES = {
'default': ('pt', 'es', 'en'),
'fr': ('es',),
'uk': ('fr',)
}
The default key is required and its value denote languages which are always tried at the end.
With such a setting:
- for uk the order of fallback languages is:
('ru', 'en', 'de', 'fr') - for fr the order of fallback languages is:
('de', 'en')- Note, that fr obviously is not a fallback, since its active language and de would be tried before en - for en and de the fallback order is
('de', 'fr')and('en', 'fr'), respectively - for any other language the order of fallback languages is just
('en', 'de', 'fr')
What is more, fallback languages order can be overridden per model, using TranslationOptions:
class NewsTranslationOptions(TranslationOptions):
fields = ('title', 'text',)
fallback_languages = {'default': ('fa', 'km')} # use Persian and Khmer as fallback for News
Dict syntax is only allowed there.
Even more, all fallbacks may be switched on or off for just some exceptional block of code using:
from modeltranslation.utils import fallbacks
with fallbacks(False):
# Work with values for the active language only
Fallback values¶
But what if current language and all fallback languages yield no field value? Then modeltranslation will use the field’s fallback value, if one was defined.
Fallback values are defined in TranslationOptions, for example:
class NewsTranslationOptions(TranslationOptions):
fields = ('title', 'text',)
fallback_values = _('-- sorry, no translation provided --')
In this case, if title is missing in active language and any of fallback languages, news title
will be '-- sorry, no translation provided --' (maybe translated, since gettext is used).
Empty text will be handled in same way.
Fallback values can be also customized per model field:
class NewsTranslationOptions(TranslationOptions):
fields = ('title', 'text',)
fallback_values = {
'title': _('-- sorry, this news was not translated --'),
'text': _('-- please contact our translator (translator@example.com) --')
}
If current language and all fallback languages yield no field value, and no fallback values are defined, then modeltranslation will use the field’s default value.
Fallback undefined¶
Another question is what do we consider “no value”, on what value should we fall back to other translations? For text fields the empty string can usually be considered as the undefined value, but other fields may have different concepts of empty or missing values.
Modeltranslation defaults to using the field’s default value as the undefined value (the empty
string for non-nullable CharFields). This requires calling get_default for every field
access, which in some cases may be expensive.
If you’d like to fall back on a different value or your default is expensive to calculate, provide a custom undefined value (for a field or model):
class NewsTranslationOptions(TranslationOptions):
fields = ('title', 'text',)
fallback_undefined = {
'title': 'no title',
'text': None
}
The State of the original field¶
As defined by the Rules for Translated Field Access, accessing the original field is guaranteed to work on the associated translation field of the current language. This applies to both, read and write operations.
The actual field value (which can still be accessed through instance.__dict__[‘original_field_name’]``) however has to be considered undetermined once the field has been registered for translation. Attempts to keep the value in sync with either the default or current language’s field value has raised a boatload of unpredictable side effects in older versions of modeltranslation.
Warning
Do not rely on the underlying value of the original field in any way!